/*******************************************************************************
* Copyright (c) 2009 EclipseSource and others. All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* EclipseSource - initial API and implementation
******************************************************************************/
package org.eclipse.ecf.tutorial.osgi.services.discovery;
import org.eclipse.ecf.internal.osgi.services.discovery.*;
import java.io.Serializable;
import java.net.*;
import java.util.*;
import org.eclipse.ecf.core.identity.*;
import org.eclipse.ecf.core.util.ECFRuntimeException;
import org.eclipse.ecf.core.util.Trace;
import org.eclipse.ecf.discovery.*;
import org.eclipse.ecf.discovery.identity.*;
import org.eclipse.ecf.osgi.services.discovery.ECFServicePublication;
import org.eclipse.ecf.remoteservice.Constants;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.discovery.*;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* ServicePublicationHandler bi-directionally connects ECF Discovery API with
* RFC119 ServicePublication.
*/
public class ServicePublicationHandler implements ServiceTrackerCustomizer, Discovery {
/**
* This map acts as a mapping from ServiceReferences to IServiceInfo objects
* and is used to retrieve the IServiceInfo associated with a given
* ServiceReference on OSGi service unpublish
*/
private Map serviceRefToServiceInfo = Collections.synchronizedMap(new HashMap());
/*---------------- ECF Discovery --> RFC 119 ----------------*/
/**
* ECF Discovery IServiceListener. Receives IServiceEvents fired by ECF
* discovery upon service discovery/un-discovery
*/
private final IServiceListener serviceListener = new IServiceListener() {
/* (non-Javadoc)
* @see org.eclipse.ecf.discovery.IServiceListener#serviceDiscovered(org.eclipse.ecf.discovery.IServiceEvent)
*/
public void serviceDiscovered(IServiceEvent anEvent) {
IServiceInfo serviceInfo = anEvent.getServiceInfo();
IServiceID serviceID = serviceInfo.getServiceID();
trace("handleOSGIServiceDiscovered", " serviceInfo=" + serviceInfo); //$NON-NLS-1$ //$NON-NLS-2$
if (checkValid(serviceID)) {
trace("handleOSGIServiceDiscovered matched", " serviceInfo=" + serviceInfo); //$NON-NLS-1$ //$NON-NLS-2$
DiscoveredServiceTracker[] discoveredTrackers = findMatchingDiscoveredServiceTrackers(serviceInfo);
for (int i = 0; i < discoveredTrackers.length; i++) {
discoveredTrackers[i].serviceChanged(new DiscoveredServiceNotificationImpl(
DiscoveredServiceNotification.AVAILABLE, serviceInfo));
}
}
}
/* (non-Javadoc)
* @see org.eclipse.ecf.discovery.IServiceListener#serviceUndiscovered(org.eclipse.ecf.discovery.IServiceEvent)
*/
public void serviceUndiscovered(IServiceEvent anEvent) {
IServiceInfo serviceInfo = anEvent.getServiceInfo();
IServiceID serviceID = serviceInfo.getServiceID();
if (checkValid(serviceID)) {
trace("handleOSGIServiceUndiscovered", " serviceInfo=" + serviceInfo); //$NON-NLS-1$ //$NON-NLS-2$
DiscoveredServiceTracker[] discoveredTrackers = findMatchingDiscoveredServiceTrackers(serviceInfo);
for (int i = 0; i < discoveredTrackers.length; i++) {
discoveredTrackers[i].serviceChanged(new DiscoveredServiceNotificationImpl(
DiscoveredServiceNotification.UNAVAILABLE, serviceInfo));
}
}
}
};
/**
* Creates a new {@link ServicePublicationHandler} and immediately starts listening for ECF discovery events
*/
public ServicePublicationHandler() {
//TODO tutorial "implement listener registration with ECF discovery"
throw new UnsupportedOperationException("ServicePublicationHandler()");
}
/*---------------- RFC 119 --> ECF Discovery ----------------*/
/* (non-Javadoc)
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
*/
public Object addingService(ServiceReference reference) {
if(checkValid(reference)) {
publishService(reference);
}
return Activator.getDefault().getContext().getService(reference);
}
/* (non-Javadoc)
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference, java.lang.Object)
*/
public void modifiedService(ServiceReference reference, Object service) {
if(checkValid(reference)) {
unpublishService(reference);
publishService(reference);
}
}
/* (non-Javadoc)
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
*/
public void removedService(ServiceReference reference, Object service) {
if(checkValid(reference)) {
unpublishService(reference);
}
}
/*----------- Converter methods RFC 119 --> ECF Discovery -----------*/
/**
* Publishes the given ServiceReference with ECF discovery
* @param reference The {@link ServiceReference} to be published
*/
private void publishService(ServiceReference reference) {
//TODO tutorial "implement servicePublicationProperties to IServiceType conversion"
throw new UnsupportedOperationException("publishService(ServiceReference reference)");
}
/**
* Unpublishes the given ServiceReference
* @param reference
*/
private void unpublishService(ServiceReference reference) {
IServiceInfo svcInfo = null;
try {
svcInfo = (IServiceInfo) serviceRefToServiceInfo.remove(reference);
if (svcInfo != null) {
Activator.getDefault().getAdvertiser().unregisterService(svcInfo);
}
} catch (ECFRuntimeException e) {
logError("publishService", "Cannot unregister serviceInfo=" //$NON-NLS-1$ //$NON-NLS-2$
+ svcInfo, e);
}
}
/**
*
* @param serviceReference
* @param serviceInfo
* @return
*/
private boolean matchWithDiscoveredServiceInfo(ServiceReference serviceReference, IServiceInfo serviceInfo) {
//TODO tutorial "implement servicePublicationProperties to IServiceType conversion"
//throw new UnsupportedOperationException("matchWithDiscoveredServiceInfo(ServiceReference serviceReference, IServiceInfo serviceInfo)");
// If short on time, returning true here is fine too
return true;
}
/**
* Returns an array of {@link DiscoveredServiceTracker}s responsible for the given {@link IServiceInfo} or an empty array if the is none
*/
private DiscoveredServiceTracker[] findMatchingDiscoveredServiceTrackers(IServiceInfo serviceInfo) {
ServiceReference[] sourceTrackers = Activator.getDefault().getDiscoveredServiceTrackerReferences();
if (sourceTrackers == null) {
return new DiscoveredServiceTracker[0];
}
List matchingTrackers = new ArrayList();
BundleContext context = Activator.getDefault().getContext();
for (int i = 0; i < sourceTrackers.length; i++) {
if (matchWithDiscoveredServiceInfo(sourceTrackers[i], serviceInfo)) {
matchingTrackers.add(context.getService(sourceTrackers[i]));
}
}
return (DiscoveredServiceTracker[]) matchingTrackers.toArray(new DiscoveredServiceTracker[] {});
}
/**
* Checks the given ServiceReference for validity with this SPH. In order for a {@link ServiceReference}
* to be valid, certain properties have to be set:
* <p>
* {@link ServicePublication#PROP_KEY_SERVICE_INTERFACE_NAME\
* {@link ServicePublication#PROP_KEY_SERVICE_PROPERTIES}
* {@link ECFServicePublication#PROP_KEY_ENDPOINT_CONTAINERID}
* {@link Constants#SERVICE_NAMESPACE}
* {@link Constants#SERVICE_ID}
* @param reference The reference to be checked
* @return true if the given reference conforms to our requirements, false otherwise
*/
private boolean checkValid(ServiceReference reference) {
// ServicePublication.PROP_KEY_SERVICE_INTERFACE_NAME
Collection svcInterfaces = ServicePropertyUtils.getCollectionProperty(reference,
ServicePublication.PROP_KEY_SERVICE_INTERFACE_NAME);
if (svcInterfaces == null) {
logError("handleServicePublication", //$NON-NLS-1$
"ignoring " //$NON-NLS-1$
+ reference + ". ServicePublication.PROP_KEY_SERVICE_INTERFACE_NAME not set", //$NON-NLS-1$
null);
return false;
}
// We also use the optional RFC 119 property PROP_KEY_SERVICE_PROPERTIES
Map servicePublicationServiceProperties = ServicePropertyUtils.getMapProperty(reference,
ServicePublication.PROP_KEY_SERVICE_PROPERTIES);
if (servicePublicationServiceProperties == null) {
logError("handleServicePublication", //$NON-NLS-1$
"ignoring " //$NON-NLS-1$
+ reference + ". ServicePublication.PROP_KEY_SERVICE_PROPERTIES not set", //$NON-NLS-1$
null);
return false;
}
// ECFServicePublication.PROP_KEY_ENDPOINT_CONTAINERID
ID endpointContainerID = (ID) reference.getProperty(ECFServicePublication.PROP_KEY_ENDPOINT_CONTAINERID);
if (endpointContainerID == null) {
logError("handleServicePublication", //$NON-NLS-1$
"ignoring " //$NON-NLS-1$
+ reference + ". ECFServicePublication.PROP_KEY_ENDPOINT_CONTAINERID not set", //$NON-NLS-1$
null);
return false;
}
// Constants.SERVICE_NAMESPACE
String rsnamespace = ServicePropertyUtils.getStringProperty(reference, Constants.SERVICE_NAMESPACE);
if (rsnamespace == null) {
logError("handleServicePublication", "ignoring " + reference //$NON-NLS-1$ //$NON-NLS-2$
+ ". Constants.SERVICE_NAMESPACE not set", null); //$NON-NLS-1$
return false;
}
// Constants.SERVICE_ID
Long remoteServiceID = (Long) reference.getProperty(Constants.SERVICE_ID);
if (remoteServiceID == null) {
logError("handleServicePublication", "ignoring " + reference //$NON-NLS-1$ //$NON-NLS-2$
+ ". Constants.SERVICE_ID not set", null); //$NON-NLS-1$
return false;
}
return true;
}
private void addPropertiesToDiscoveryServiceProperties(IServiceProperties discoveryServiceProperties,
Map servicePublicationServiceProperties) {
for (Iterator i = servicePublicationServiceProperties.keySet().iterator(); i.hasNext();) {
Object key = i.next();
if (!(key instanceof String)) {
trace("addPropertiesToDiscoveryServiceProperties", //$NON-NLS-1$
"skipping non-string key " + key); //$NON-NLS-1$
continue;
}
String keyStr = (String) key;
Object val = servicePublicationServiceProperties.get(keyStr);
if (val instanceof String) {
discoveryServiceProperties.setPropertyString(keyStr, (String) val);
} else if (val instanceof byte[]) {
discoveryServiceProperties.setPropertyBytes(keyStr, (byte[]) val);
} else if (val instanceof Serializable) {
discoveryServiceProperties.setProperty(keyStr, val);
}
}
}
/*----------- Converter methods ECF Discovery --> RFC 119 -----------*/
/**
* Only accept services of type ECFServicePublication.SERVICE_TYPE
*/
private boolean checkValid(IServiceID serviceId) {
String[] service = serviceId.getServiceTypeID().getServices();
List asList = Arrays.asList(service);
if (asList.contains(ECFServicePublication.SERVICE_TYPE)) {
return true;
}
return false;
}
/**
* Creates an {@link URI} from the given {@link ID}
* @param endpointContainerID
* @return an {@link URI}
* @throws URISyntaxException
*/
private URI createURI(ID endpointContainerID) throws URISyntaxException {
boolean done = false;
URI uri = null;
String str = endpointContainerID.getName();
while (!done) {
try {
uri = new URI(str);
if (!uri.isOpaque()) {
done = true;
} else {
str = uri.getRawSchemeSpecificPart();
}
} catch (URISyntaxException e) {
done = true;
}
}
String scheme = ECFServicePublication.SERVICE_TYPE;
int port = 32565;
if (uri != null) {
port = uri.getPort();
if (port == -1)
port = 32565;
}
String host = null;
try {
host = InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
host = "localhost"; //$NON-NLS-1$
}
return new URI(scheme, null, host, port, null, null, null);
}
/**
* Creates an {@link IServiceTypeID} from the given servicePublicationProperties
* @param servicePublicationProperties
* @param aNamespace
* @return
* @throws IDCreateException
*/
private IServiceTypeID createServiceTypeID(Map servicePublicationProperties, Namespace aNamespace) throws IDCreateException {
//TODO tutorial "implement servicePublicationProperties to IServiceType conversion"
throw new UnsupportedOperationException("createServiceTypeID(Map servicePublicationProperties, Namespace aNamespace)");
}
/*----------- Utility methods -----------*/
/**
* Returns the object associated with the given key from the map or default if map does not contains key
*/
private String getPropertyWithDefault(Map properties, String key, String def) {
String val = (String) properties.get(key);
return (val == null) ? def : val;
}
/**
* Log an Error with the OSGi LogService. Additionally sends a trace.
*/
private void logError(String method, String message, Throwable t) {
Activator.getDefault().log(method, message, t);
}
/**
* Trace output
*/
private void trace(String methodName, String message) {
Trace.trace(Activator.PLUGIN_ID, DebugOptions.SVCPUBHANDLERDEBUG, this.getClass(), methodName, message);
}
/**
* Cleans up after bundle life-cycle stop event. Most importantly it unregisters all registered services with ECF discovery
*/
public void dispose() {
IDiscoveryLocator locator = Activator.getDefault().getLocator();
if (locator != null) {
locator.removeServiceListener(serviceListener);
synchronized (serviceRefToServiceInfo) {
for (Iterator i = serviceRefToServiceInfo.keySet().iterator(); i.hasNext();) {
ServiceReference sr = (ServiceReference) i.next();
unpublishService(sr);
}
serviceRefToServiceInfo.clear();
}
locator = null;
}
}
}